#include "ChangerSysIcon.h"
#include "OptionsDialog.h"
#include "ImageDialog.h"
#include <QtGui>
#include <QtXml>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <cmath>

#ifdef Q_OS_WIN32
#include <windows.h>
#include <stdio.h>
//#define SPI_GETDESKWALLPAPER 115;
#endif

enum ApplyMethod {
    Center,
    Tile,
    ScaleIgnoreRatio,
    ScaleShrinkToRatio,
    ScaleExpandToRatio,
};

class Wallpaper {
protected:
    QDomElement e;

    QString getLocation();
    QString getMethodString(ApplyMethod);
    QByteArray getHash(
            QString fileName,
            QCryptographicHash::Algorithm algorithm = QCryptographicHash::Md5);
    QSize getScreenDimensions();
public:
    Wallpaper(QDomElement);
    void apply();

    QDomElement element() {return e;}
};

Wallpaper::Wallpaper(QDomElement e) {
    this->e = e;
}

QByteArray Wallpaper::getHash(QString fileName, QCryptographicHash::Algorithm algorithm) {
   QFile file(e.attribute("file"));
   QByteArray alnumVersion;
   if (file.open(QIODevice::ReadOnly)) {
       QCryptographicHash hash(algorithm);
       while (!file.atEnd()) {
           hash.addData(file.read(8192));
       }
       QByteArray value = hash.result();
       foreach (unsigned char c, value) {
           unsigned char one = c & 0x0F, two = (c & 0xF0) >> 4;
           if (one < 10) one += 48; else one += 97;
           if (two < 10) two += 48; else two += 97;
           alnumVersion += one;
           alnumVersion += two;
       }
       file.close();
       return alnumVersion;
    } else {
        return QByteArray();
    }
}

QString Wallpaper::getMethodString(ApplyMethod v) {
    switch (v) {
    case Center: return "center"; break;
    case Tile: return "tile"; break;
    case ScaleIgnoreRatio: return "ir"; break;
    case ScaleShrinkToRatio: return "str"; break;
    case ScaleExpandToRatio: return "etr"; break;
    default: return QString(); break;
    }
}

QSize Wallpaper::getScreenDimensions() {
#if defined(Q_OS_WIN32)
    HDC hdc = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
    assert(hdc);
    QSize size(GetDeviceCaps(hdc, HORZRES), GetDeviceCaps(hdc, VERTRES));
    DeleteDC(hdc);
    return size;
#else
    return QSize();
#endif
}

QString Wallpaper::getLocation() {
    QSettings settings;
    QString location = e.attribute("file");
    QFileInfo info(location);
    QString method = e.attribute("method", settings.value("display").toString());
    if (info.suffix().compare("bmp", Qt::CaseInsensitive)
            || (method != "tile" && method != "center")) {
        //create hash of file contents for comparison
        QFile file(location);
        QByteArray contents;
        QByteArray hash = getHash(location);
        //calculate filename for temp file
        QSize screen = getScreenDimensions();
        QString tempLocation = QDir::temp().filePath(QString("-%1-%2.%3.bmp")
                .prepend(hash.left(8)) //shortening the hash for less cumbersome filenames
                .arg(screen.width())
                .arg(screen.height())
                .arg(method));
        qDebug() << "Temp file:" << tempLocation << ',' << QFile::exists(tempLocation);
        if (!QFile::exists(tempLocation)) {
            //file processed to specifications does not exist, so create it
            QImage temp(location);
            if (method == "etr") {
                temp = temp.scaled(screen, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
            } else if (method == "str") {
                temp = temp.scaled(screen, Qt::KeepAspectRatio, Qt::SmoothTransformation);
            } else if (method == "ir") {
                temp = temp.scaled(screen, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
            }
            temp.save(tempLocation, "BMP");
            qDebug() << "saved";
        }
        return tempLocation;
    } else {
        return location;
    }
}

void Wallpaper::apply() {
#if defined(Q_OS_WIN32)
    {
    QSettings settings;
    QSettings registry("HKEY_CURRENT_USER\\Control Panel\\Desktop",
            QSettings::NativeFormat);
    QString method = settings.value("display").toString();
    QString myMode = e.attribute("method");
    if (myMode == "tile") {
        method = "tile";
    } else if (!myMode.isNull()) {
        method = "center";
    }
    if (method == "center") {
        registry.setValue("WallpaperStyle", "0");
        registry.setValue("TileWallpaper", "0");
    ////We prescale the image to fit, so this isn't necessary.
    //} else if (method == "stretch") {
    //    registry.setValue("WallpaperStyle", "2");
    //    registry.setValue("TileWallpaper", "0");
    } else if (method == "tile") {
        registry.setValue("WallpaperStyle", "1");
        registry.setValue("TileWallpaper", "1");
    }
    }
#endif
    QString newPaper = getLocation();
#if defined(Q_OS_WIN32)
    //XXX
    //wchar_t file[newPaper.size()];
    //newPaper.toWCharArray(file);
    if (!newPaper.isNull() && !SystemParametersInfoA(
                SPI_SETDESKWALLPAPER, 0, newPaper.toAscii().data(),
                SPIF_UPDATEINIFILE|SPIF_SENDWININICHANGE)) {
        LPVOID lpMsgBuf;
        DWORD dw = GetLastError();
        FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                dw,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPTSTR) &lpMsgBuf,
                0, NULL);
        MessageBox(NULL, (const WCHAR *)lpMsgBuf, TEXT("Error"), MB_OK);
        LocalFree(lpMsgBuf);
        ExitProcess(dw);
    }
    
    SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
#elif defined(Q_OS_LINUX)
    #warning "Linux is not yet supported."
#else
    #error "Unsupported operating system"
#endif
}


ChangerSysIcon::ChangerSysIcon(QWidget *parent)
        : QSystemTrayIcon(parent), wallpaperDoc("Wallpaper") {
    setIcon(QIcon(":/icons/display-16.png"));
    setToolTip(tr("Wallpaper Changer"));
    changeTimer = new QTimer(this);
    QSettings settings;

    //load the default settings from file and set any that aren't set already
    QSettings defaults(":/defaults.ini", QSettings::IniFormat);
    foreach (QString key, defaults.allKeys()) {
        if (settings.value(key).isNull()) {
            settings.setValue(key, defaults.value(key));
        }
    }

    connect(changeTimer, SIGNAL(timeout()), SLOT(changeWallpaper()));
    connect(this, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
            SLOT(activatedSlot(QSystemTrayIcon::ActivationReason)));

    menu = new QMenu;

    QAction *nextItem = menu->addAction(tr("&Next"), this, SLOT(changeWallpaper()));
    menu->setDefaultAction(nextItem);

    menu->addAction(tr("&Images..."), this, SLOT(showImages()));

    menu->addAction(tr("&Options..."), this, SLOT(showOptions()));
    menu->addSeparator();
    menu->addAction(tr("E&xit"), QCoreApplication::instance(), SLOT(quit()));
    setContextMenu(menu);

    changeTimer->start(settings.value("time").toInt()*60*1000);

    //Load initial image state from XML
    QFile file("wallpaper.xml");
    if (file.open(QIODevice::ReadOnly)) {
        wallpaperDoc.setContent(&file);
        file.close();
    } else {
        QDomElement e = wallpaperDoc.createElement("wallpaper");
        wallpaperDoc.appendChild(e);
    }

    changeWallpaper();
}

ChangerSysIcon::~ChangerSysIcon() {
    //Write XML
    QFile file("wallpaper.xml");
    if (!file.open(QIODevice::WriteOnly)) {
        return;
    }
    QTextStream out(&file);
    wallpaperDoc.save(out, 4);

    delete menu;
    delete imageWin;
}

void ChangerSysIcon::activatedSlot(QSystemTrayIcon::ActivationReason reason) {
    switch (reason) {
    case QSystemTrayIcon::Trigger:
        changeWallpaper();
        break;
    case QSystemTrayIcon::DoubleClick:
        break;
    case QSystemTrayIcon::MiddleClick:
        break;
    case QSystemTrayIcon::Context:
        break;
    case QSystemTrayIcon::Unknown:
        break;
    }
}

void ChangerSysIcon::showImages() {
    if (!imageWin) {
        imageWin = new ImageWindow(wallpaperDoc.documentElement());
        imageWin->show();
    } else {
        imageWin->setFocus(Qt::OtherFocusReason);
    }
}

void ChangerSysIcon::showOptions() {
    QSettings settings;
    int time = settings.value("time").toInt();
    QString mode = settings.value("mode").toString();
    QString method = settings.value("display").toString();
    QString directory = settings.value("directory").toString();
    OptionsDialog dialog;
    if (dialog.exec()) {
        int newTime = settings.value("time").toInt();
        QString newMode = settings.value("mode").toString();
        QString newMethod = settings.value("display").toString();
        QString newDirectory = settings.value("directory").toString();
        if (time != newTime) changeTimer->start(newTime*60*1000);
        if (mode != newMode || method != newMethod || directory != newDirectory) changeWallpaper();
    }
}

QString ChangerSysIcon::getCurrentWallpaper() {
#if defined(Q_OS_WIN32)
    char chars[1024];
    SystemParametersInfoA(SPI_GETDESKWALLPAPER, 1024, chars, 0);
    return QString(chars);
#else
#endif
    return QString();
}

QFileInfoList ChangerSysIcon::getImageList() {
    QSettings settings;
    QStringList imageNameFilters;
    foreach (QByteArray format, QImageReader::supportedImageFormats()) {
        imageNameFilters.append(QString("*.") + QString(format).toLower());
    }
    QFileInfoList list;
    QDir dir(settings.value("directory").toString());
    QDirIterator iter(dir.absolutePath(),
            imageNameFilters,
            QDir::Files,
            QDirIterator::Subdirectories|QDirIterator::FollowSymlinks);
    while (iter.hasNext()) {
        list.append(QFileInfo(iter.next()));
    }
    return list;
}

void ChangerSysIcon::changeWallpaper() {
#if defined(Q_OS_WIN32)
    SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);
#endif
    QSettings settings;

    //* Read list of files on disk
    QFileInfoList list = getImageList();
    QList < Wallpaper * > paperList;
    if (!list.size()) return;
    
    //* Refresh file list
    Wallpaper *newPaper = NULL;
    QString current = getCurrentWallpaper();
    QMap < int, Wallpaper * > sortedFiles;
    QHash < QString, Wallpaper * > fileHash;
    QDomElement docElem = wallpaperDoc.documentElement();
    QDomNode n = docElem.firstChild();
    qDebug() << "before count" << docElem.childNodes().count();
    while (!n.isNull()) {
        QDomElement e = n.toElement();
        if (!e.isNull() && e.tagName() == "image") {
            QFileInfo fromXml(e.attribute("file"));
            if (fromXml.exists() && list.contains(fromXml)) {
                Wallpaper *wallpaper = new Wallpaper(e);
                fileHash[e.attribute("file")] = wallpaper;
                if (e.attribute("enabled").toInt()) {
                    sortedFiles.insertMulti(e.attribute("lastused").toInt(), wallpaper);
                }
                n = n.nextSibling();
            } else {
                qDebug() << "cleared" << e.attribute("file");
                QDomNode old = n;
                n = n.nextSibling();
                docElem.removeChild(old);
            }
        } else {
            n = n.nextSibling();
        }
    }
    qDebug() << "after count" << docElem.childNodes().count();
    foreach (QFileInfo info, list) {
        QString path = info.absoluteFilePath();
        if (!fileHash.contains(path)) {
            QDomElement e = wallpaperDoc.createElement("image");
            e.setAttribute("enabled", 1);
            e.setAttribute("file", path);
            e.setAttribute("lastused", 0);
            docElem.appendChild(e);
            Wallpaper *wallpaper = new Wallpaper(e);
            paperList.append(wallpaper);
            fileHash[info.absoluteFilePath()] = wallpaper;
            sortedFiles.insertMulti(0, wallpaper);
        }
    }

    if (settings.value("mode") == "random") {
        //a "more-random-than-random" algorithm which randomly chooses
        //an image in the bottom 3/4s of shown images 80% of the time
        //and chooses a random image out of all of the images 20% of the time
        int rnd = std::rand() % 100;
        int index = 0;
        if (rnd < 80) {
            index = std::rand() % int(sortedFiles.size() * .75);
        } else {
            index = std::rand() % int(sortedFiles.size() - 1);
        }
        newPaper = sortedFiles.values()[index];
        QDomElement element = newPaper->element();
        qDebug() << "index" << index << "abs" << element.attribute("file");
        //set it larger than the largest value
        element.setAttribute("lastused", sortedFiles.keys()[sortedFiles.size()-1] + 1);
    } else if (settings.value("mode") == "sequential") {
        //this simply increments the index in the sorted image list
        int index = (settings.value("sequentialPos", -1).toInt() + 1) % list.size();
        newPaper = paperList[index];
        qDebug() << "index" << index << "abs" << newPaper->element().attribute("file");
        settings.setValue("sequentialPos", index);
    }
    newPaper->apply();

    foreach (Wallpaper *wallpaper, fileHash.values()) {
        delete wallpaper;
    }
}

